﻿#coding:utf-8

"""
RFC792, echo/reply message:
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |     Code      |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Data ...
+-+-+-+-+-
"""

import os
import select
import socket
import struct
import sys
import time

ICMP_ECHO_REQUEST = 8

def checksum(source_string):
	sum = 0
	count_to = (len(source_string) / 2) * 2
	for count in xrange(0, count_to, 2):
		this = ord(source_string[count + 1]) * 256 + ord(source_string[count])
		sum = sum + this
		sum = sum & 0xffffffff

	if count_to < len(source_string):
		sum = sum + ord(source_string[len(source_string) - 1])
		sum = sum & 0xffffffff

	sum = (sum >> 16) + (sum & 0xffff)
	sum = sum + (sum >> 16)
	answer = ~sum
	answer = answer & 0xffff

	answer = answer >> 8 | (answer << 8 & 0xff00)

	return answer

def receive_one_ping(my_socket, id, timeout):  #获取返回信息
	time_left = timeout
	while True:
		started_select = time.time()
#		print 'started_select:%s' % started_select
		what_ready = select.select([my_socket], [], [], time_left) #等待一个socket,返回消息
#		print what_ready
		how_long_in_select = (time.time() - started_select)  #
#		print how_long_in_select
		if what_ready[0] == []: #timeout
			return

		time_received = time.time()
		received_packet, addr = my_socket.recvfrom(1024)
#		print received_packet, addr
		icmpHeader = received_packet[20:28]
#		print icmpHeader
		type, code, checksum, packet_id, sequence = struct.unpack("bbHHh", icmpHeader)
#		print type, code, checksum, packet_id, sequence
		if packet_id == id:
			bytes = struct.calcsize("d")
			time_sent = struct.unpack("d", received_packet[28:28 + bytes])[0]
			print time_received - time_sent
			return time_received - time_sent  #返回ping耗时

		time_left = time_left - how_long_in_select
		if time_left <= 0:
			return

def send_one_ping(my_socket, dest_addr, id, psize): #发送一个ping包
	dest_addr = socket.gethostbyname(dest_addr)  #域名转IP
	
	#从数据包中取出header,64-8=56
	psize = psize - 8
	
	#header is type(8),code(8),checksum(16),id(16),sequence(16)
	my_checksum = 0
	
	#
	header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, my_checksum, id, 1)
	#b(signed char有符号字符),H(unsigned short无符号短整型),h(short短整型)
	bytes = struct.calcsize("d") #8 byte
	data = (psize - bytes) * "Q" #56-8=48,48个Q作为数据
	data = struct.pack("d", time.time()) + data
	
	#
	my_checksum = checksum(header + data)
	
	#
	header = struct.pack("bbHHh", ICMP_ECHO_REQUEST, 0, socket.htons(my_checksum), id, 1)
	packet = header + data
	my_socket.sendto(packet, (dest_addr, 1))
	
def do_one(dest_addr, timeout, psize):
	icmp = socket.getprotobyname("icmp") #返回该协议的
	try:
		my_socket = socket.socket(socket.AF_INET, socket.SOCK_RAW, icmp)
	except socket.error, (error, msg):
		if error == 1:
			#
			msg = msg + (
					" - Note that ICMP messages can only be sent from processes"
					"running as root."
					)
			raise socket.error(msg)
		raise
		
	my_id = os.getpid() & 0xFFFF  #返回进程pid
	
	send_one_ping(my_socket, dest_addr, my_id, psize)
	delay = receive_one_ping(my_socket, my_id, timeout)
	
	my_socket.close()
	return delay
	
def verbose_ping(dest_addr, timeout = 2, count = 4, psize = 64):
	for i in xrange(count):
		print "ping %s with ..." % dest_addr,
		try:
			delay  =  do_one(dest_addr, timeout, psize)
		except socket.gaierror, e:
			print "failed. (socket error: '%s')" % e[1]
			break

		if delay  ==  None:
			print "failed. (timeout within %ssec.)" % timeout
		else:
			delay  =  delay * 1000
			print "get ping in %0.4fms" % delay
	print
	
def quiet_ping(dest_addr, timeout = 2, count = 4, psize = 64):
	mrtt = None
	artt = None
	lost = 0
	plist = []
	#delay = do_one(dest_addr, timeout, psize)
	for i in xrange(count):
		try:
			delay = do_one(dest_addr, timeout, psize)
		except socket.gaierror, e:
			print "failed. (socket error: '%s')" % e[1]
			break

		#delay = do_one(dest_addr, timeout, psize)
		if delay != None:
			delay = delay * 1000
			plist.append(delay)

	# Find lost package percent
	percent_lost = 100 - (len(plist) * 100 / count)

	# Find max and avg round trip time
	if plist:
		mrtt = max(plist)
		artt = sum(plist) / len(plist)

	return percent_lost, mrtt, artt
	
if __name__ == '__main__':
	quiet_ping("www.baidu.com")

